package org.lightpersist.hibernate;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.lightcommons.logger.Log;
import org.lightcommons.logger.LogFactory;
import org.lightcommons.util.Assert;
import org.lightcommons.util.ReflectionUtils;
import org.lightcommons.util.StringUtils;
import org.lightpersist.annotations.Query;
import org.lightpersist.dao.hibernate.HibernateTemplate;

/**
 * <pre>
 * 为@Query注解的方法提供一个和Grails dynamic finder 类似的查询方法
 * 如:
 * &#64;Query(maxResult=100) Author[] findByNameLikeOrAgeLessThan(String name,int age);
 *    调用：
 *    Author[] findByNameLikeOrAgeLessThan("司马%",25)
 *  等价于 daoTemplate.findByQuery("from Author where name like ? or age &lt ?","司马%",25)
 *  返回前100条
 * 
 *    &#64;Query ...
 *    Author getByName("司马迁")
 *  等价于 daoTemplate.findByQuery(0,1,"from Author where name = ? order by id desc","司马迁").get(0)
 *  
 *     &#64;Query(maxResult=50,orderBy="id desc") ...
 *    Author getPageByNameLike("司马%")
 *  等价于 daoTemplate.getPageByQuery("from Author where name = ? ","select count(*) from Author where name = ? ","司马迁")
 *  每页50条
 * </pre>
 *  
 *  <pre>
 * 支持的函数:
 * 
 * T getBy*(hqlParam,...[,String orderBy][,int startIndex])
 * 返回单个实体，可选参数 orderBy,startIndex,
 * 
 * Page&lt;T&gt; getPageBy*(hqlParam,...[,int pageIndex,int maxResult][,String orderBy])
 * 返回Page对象，可选参数 pageIndex 必须定义在 maxResult 之前
 * 
 * T[] findBy*(hqlParam,...[,int startIndex,int maxResult][,String orderBy])
 * List&lt;T&gt; findBy* startIndex 必须定义在 maxResult 之前
 * 
 * 将会支持的函数名:
 * int|double|long|... count*By*
 * int|double|long|... sum*By*
 * int|double|long|... avg*By*
 * deleteBy*
 * 
 * 语法：
 * findBy([Property][Comparator][Boolean Operator])?[Property][Comparator](?,?,?)
 * 
 * 操作符：
 * The following operator names can be used within the respective dynamic methods:

	* LessThan
	* LessThanEquals
	* GreaterThan
	* GreaterThanEquals
	* Between
	* InList (not support yet)
	* Like
	* Ilike (i.e. ignorecase like)
	* IsNotNull
	* IsNull
	* Not
	* Equal(默认)
	* NotEqual
	* And
	* Or
	* 
 * 参数：
 * 参数数量及参数类型由表达式决定，依次填写表达式需要的参数
 * 扩展参数：
 * getBy*(hqlparams... [,String orderBy[,int startIndex]])
 * 
 * Create On: 2008-12-25
 * </pre>
 * @see Query Query Annotation
 * @author GL
 *
 */
public class AutoImplementInterceptor implements MethodInterceptor {

	private static Log log = LogFactory.getLog(AutoImplementInterceptor.class);
	
	//getBy*  return 1
	//findBy* return list
	//getPageBy* return page
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
//		if(!Modifier.isAbstract(method.getModifiers()))
//			return invocation.proceed();
		Query query = method.getAnnotation(Query.class);
		if(query==null)
			return invocation.proceed();
		Class<?> returnClass = method.getReturnType();
		Class<?>[] paramClasses = method.getParameterTypes();
		String methodName = method.getName();
		//getBy* auto Implementation
		if(methodName.matches("^deleteBy[A-Z].*")){
			String methodSuffix = methodName.substring(5);
			return deleteBy(returnClass, methodSuffix, invocation.getArguments(),paramClasses,query);
		}
		
		if(returnClass==null)
			return null;
		
		if(methodName.matches("^getBy[A-Z].*")){
			String methodSuffix = methodName.substring(5);
			return getBy(returnClass, methodSuffix, invocation.getArguments(),paramClasses,query);
			
		}else if(methodName.matches("^findBy[A-Z].*") && (returnClass.isAssignableFrom(List.class) || returnClass.isArray())){
			String methodSuffix = methodName.substring(6);
			Type returnType = invocation.getMethod().getGenericReturnType();
			Class<?> domainClass = ReflectionUtils.getRawClass(ReflectionUtils.getComponentType(returnType));
			return findBy(domainClass, methodSuffix, invocation.getArguments(),paramClasses, query, returnClass);
		
		}else if(methodName.matches("^getPageBy[A-Z].*")){
			String methodSuffix = methodName.substring(9);
			Type returnType = invocation.getMethod().getGenericReturnType();
			Class<?> domainClass = ReflectionUtils.getRawClass(ReflectionUtils.getComponentType(returnType));
			return getPageBy(domainClass, methodSuffix, invocation.getArguments(),paramClasses, query);
		}
		return invocation.proceed();
	}
	
	private Object deleteBy(Class<?> returnType, String methodSuffix,
			Object[] arguments, Class<?>[] paramClasses, Query query) {
		
		throw new UnsupportedOperationException(
				"not support dynamic deleteBy yet!");
	}

	private Object getPageBy(Class<?> domainClass, String methodSuffix,
			Object[] arguments, Class<?>[] paramClasses, Query query) {
		
		ConditionDefine cd = methodNameToCondition(methodSuffix);
		String orderBy = findArgument(arguments, cd.paramCount, 0, String.class, paramClasses);
		Integer pageIndex = findArgument(arguments, cd.paramCount, 0, Integer.class, paramClasses);
		Integer maxResult = findArgument(arguments, cd.paramCount, 1, Integer.class, paramClasses);
		
		if(orderBy == null) orderBy = query.orderby();
		if(pageIndex == null) pageIndex = query.startIndex();
		if(maxResult == null) maxResult = query.maxResult();
		
		Object[] args = copyArray(arguments, cd.paramCount);
		
		String hql = buildHql(domainClass, cd.where, orderBy);
		String countQuery = "select count(*) " + hql;
		
		return getTemplate().getPageByQuery(hql, countQuery,
				pageIndex, maxResult, args);
		
	}

	private <T> T getBy(Class<T> domainClass, String methodSuffix,
			Object[] arguments, Class<?>[] paramClasses, Query query) {
		ConditionDefine cd = methodNameToCondition(methodSuffix);
		String orderBy = findArgument(arguments, cd.paramCount, 0, String.class, paramClasses);
		Integer startIndex = findArgument(arguments, cd.paramCount, 0, Integer.class, paramClasses);
//		Integer maxResult = findArgument(arguments, cd.paramCount, 1, Integer.class, paramClasses);
		
		if(orderBy == null) orderBy = query.orderby();
		if(startIndex == null) startIndex = query.startIndex();
//		if(maxResult == null) maxResult = query.maxResult();
		
		Object[] args = copyArray(arguments, cd.paramCount);
		String hql = buildHql(domainClass, cd.where, orderBy);
		
		List<T> list = getTemplate().findByQuery(startIndex, 1, hql, args);
		return list != null && list.size() > 0 ? list.get(0) : null;
		
	}

	private <T, U> Object findBy(Class<T> domainClass, String methodSuffix,
			Object[] arguments, Class<?>[] paramClasses, Query query,
			Class<?> returnClass) {
		
		ConditionDefine cd = methodNameToCondition(methodSuffix);
		String orderBy = findArgument(arguments, cd.paramCount, 0, String.class, paramClasses);
		Integer startIndex = findArgument(arguments, cd.paramCount, 0, Integer.class, paramClasses);
		Integer maxResult = findArgument(arguments, cd.paramCount, 1, Integer.class, paramClasses);
		
		if(orderBy == null) orderBy = query.orderby();
		if(startIndex == null) startIndex = query.startIndex();
		if(maxResult == null) maxResult = query.maxResult();
		
		Object[] args = copyArray(arguments, cd.paramCount);
		String hql = buildHql(domainClass, cd.where, orderBy);
		
		List<T> list = getTemplate().findByQuery(startIndex,
				maxResult, hql, args);
		
		if (returnClass.isArray()) {
			U[] ret = (U[]) Array.newInstance(returnClass.getComponentType(),list.size());
			list.toArray(ret);
			return ret;
		}
		return list;
		
	}

	/**
	 *  从 from 开始 寻找 第 index 个匹配 clazz 的 参数并返回， paramClasses 定义类型， arguments 目前参数 
	 * @param arguments
	 * @param from
	 * @param index
	 * @param clazz
	 * @param paramClasses
	 * @return
	 */
	private <T>T findArgument(Object[] arguments,int from,int index,Class<T> clazz,Class<?>[] paramClasses){
		int c=0;
		for(int i=from;i<arguments.length;i++){
			if(clazz.isAssignableFrom(ReflectionUtils.getPrimWrap(paramClasses[i]))){
				if(c==index)
					return (T) arguments[i];
				++c;
			}
		}
		return null;
	}
	
	private String buildHql(Class<?> domainClass,String condition,String orderBy){
		return "from "+domainClass.getName()+" where "+condition + 
		(StringUtils.hasText(orderBy) ? ( " order by "+orderBy): "");
	}
	
	private HibernateTemplate getTemplate(){
		return HibernateModule.getInjector().getInstance(HibernateTemplate.class);
	}
	
	private static Map<String, ConditionDefine> nameToCondition = new HashMap<String, ConditionDefine>();
	private static Map<String, Object[]> operators = new HashMap<String, Object[]>();
	private static Pattern logicPattern = Pattern
			.compile("(.*[a-z0-9])(Or|And)($|[A-Z].*)");
	private static Pattern notOperatorPattern = Pattern
			.compile("(.*[a-z0-9])(NotBetween|NotIlike|NotLike|IsNotNull|NotEqual)$");
	private static Pattern operatorPattern = Pattern
			.compile("(.*[a-z0-9])(" +
					"GreaterThanEquals|GreaterThan|LessThanEquals|LessThan|" +
					"Between|Ilike|Like|IsNotNull|IsNull|NotEqual|Equal)$");
	static{
		operators.put("Or", new Object[]{" or ",0});
		operators.put("And", new Object[]{" and ",0});
		operators.put("LessThan", new Object[]{" < ?",1});
		operators.put("LessThanEquals", new Object[]{" <= ?",1});
		operators.put("GreaterThan", new Object[]{" > ?",1});
		operators.put("GreaterThanEquals", new Object[]{" >= ?",1});
		operators.put("Between", new Object[]{" between ? and ?",2});
		operators.put("Like", new Object[]{" like ?",1});
		operators.put("Ilike", new Object[]{" like lower(?)",1});// (i.e. ignorecase like)
		operators.put("IsNotNull", new Object[]{" is not null",0});
		operators.put("IsNull", new Object[]{" is null",0});
		operators.put("Not", new Object[]{" not ",0});
		operators.put("Equal", new Object[]{" = ?",1});
		operators.put("NotEqual", new Object[]{" <> ?",1});
		operators.put("InList", new Object[]{" in ",0});
		operators.put("NotBetween", new Object[]{" not between ? and ?",2});
		operators.put("NotIlike", new Object[]{" not like lower(?)",1});
		operators.put("NotLike", new Object[]{" not like ?",1});
	}
	static class ConditionDefine{
		static ConditionDefine EMPTY=new ConditionDefine("",0);
		String where;
		int paramCount;
		
		public ConditionDefine(String where,int paramCount) {
			super();
			this.paramCount = paramCount;
			this.where = where;
		}
		@Override
		public String toString() {
			return where;
		}
	}
	ConditionDefine methodNameToCondition(String methodName){
		ConditionDefine result = nameToCondition.get(methodName);
		if(result!=null)
			return result;
		Matcher matcher = logicPattern.matcher(methodName);
		if(matcher.matches()){
			String s1=matcher.group(1);
			String op=matcher.group(2);
			String s2 = matcher.group(3);
			ConditionDefine c1 = methodNameToCondition(s1);
			op = (String) operators.get(op)[0];
			ConditionDefine c2 = s2.length()==0 ? ConditionDefine.EMPTY : methodNameToCondition(s2);
			result = new ConditionDefine(c1.where+ op + c2.where,c1.paramCount+c2.paramCount);
		}else{
			result = handleOperator(methodName);
		}
		nameToCondition.put(methodName, result);
		return result;
	}

	protected ConditionDefine handleOperator(String methodName){
		ConditionDefine result = nameToCondition.get(methodName);
		if(result!=null)
			return result;
		Matcher matcher = notOperatorPattern.matcher(methodName);
		if(!matcher.matches()){
			matcher = operatorPattern.matcher(methodName);
		}
		if(matcher.matches()){
			String s1=matcher.group(1);
			String opName=matcher.group(2);
			s1 = StringUtils.uncapitalize(s1);
			if("Ilike".equals(opName) || "NotIlike".equals(opName))
				s1 = "lower("+s1+")";
			Object[] opv = operators.get(opName);
			Assert.notNull(opv,"No operator found for "+opName);
			result = new ConditionDefine(s1+ opv[0],(Integer) opv[1]) ;
		}else if(methodName.length()>0){
			result = new ConditionDefine(StringUtils.uncapitalize(methodName)+ operators.get("Equal")[0],1);
		}else{
			result = ConditionDefine.EMPTY;
		}
		nameToCondition.put(methodName, result);
		return result;
	}

	private Object[] copyArray(Object[] args,int len){
		Object[] ret = new Object[len];
		System.arraycopy(args, 0, ret, 0, len);
		return ret;
	}
	
}
